/* @flow */

import { ASSET_TYPES } from 'shared/constants'
import { defineComputed, proxy } from '../instance/state'
import { extend, mergeOptions, validateComponentName } from '../util/index'

/**
 * 创建了一个 Sub 函数并继承了父级。如果直接使用 Vue.extend，则 Sub 继承于 Vue 构造函数
 * @param {*} Vue 
 */
export function initExtend (Vue: GlobalAPI) {
  /**
   *每个实例构造函数，包括Vue，都有一个唯一的
   *cid。这使我们能够创建包装的“子级”
   *构造函数”以进行原型继承并缓存它们。
   */ 
  Vue.cid = 0
  let cid = 1

  /**
   * 创建一个子类，继承 Vue 身上的一些功能
   */
  Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    const Super = this
    // 父类的 ID
    const SuperId = Super.cid
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    // 出于性能考虑，增加了缓存策略，只要返回结果是固定的，就可以将计算结果缓存，再次调用的时候，只需要从缓存中取出结果即可
    // 使用父类的 ID 作为缓存的 key，将子类缓存在 cachedCtors 中。
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }

    // 检查组件的名称是否合法，如果不合法，在开发环境下提示警告。
    const name = extendOptions.name || Super.options.name
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name)
    }

    // 创建子类并将其返回，此时的子类还不具备 Vue 的能力
    const Sub = function VueComponent (options) {
      this._init(options)
    }

    // 经典的寄生组合方式
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub

    // 为子类添加 ID，保证子类的唯一性
    Sub.cid = cid++

    // 合并父类选项与子类选项
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    // 将父类保存到子类的 super 属性中
    Sub['super'] = Super

    // 对于非根实例的子组件而言，prop 的代理发生在 Vue.extend 阶段。好处是不用为每个组件实例都做一层 proxy，是一种优化手段。
    if (Sub.options.props) {
      // 初始化 props 的作用是将 key 代理到 _props 中
      initProps(Sub)
    }
    if (Sub.options.computed) {
      // 将 computed 对象遍历一遍，并将里面的每一项都定义一遍
      initComputed(Sub)
    }

    // allow further extension/mixin/plugin usage

    // 将父类中存在的属性依次复制到子类中。
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.

    // 新增一些属性
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)

    // cache constructor
    cachedCtors[SuperId] = Sub
    return Sub
  }
}

/**
 * 对于非根实例的子组件而言，prop 的代理发生在 Vue.extend 阶段
 * @param {*} Comp 
 */
function initProps (Comp) {
  const props = Comp.options.props
  for (const key in props) {
    proxy(Comp.prototype, `_props`, key)
  }
}

function initComputed (Comp) {
  const computed = Comp.options.computed
  for (const key in computed) {
    defineComputed(Comp.prototype, key, computed[key])
  }
}
